package com.zst.http.forward.remote.httppacket;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.nio.client.HttpAsyncClient;
import org.apache.http.protocol.HTTP;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;


public class MultiplexHttpClient implements Closeable {
    private CloseableHttpAsyncClient httpAsyncClient = null;
    private int timeout;

    public MultiplexHttpClient(int timeout) {
        this.timeout = timeout;
    }

    public MultiplexHttpClient() {
        this.timeout = 3000;
    }

    @Override
    public void close() {
        if (httpAsyncClient != null) {
            try {
                httpAsyncClient.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            httpAsyncClient = null;
        }
    }

    public Request request() {
        return new Request(this);
    }

    CompletableFuture<HttpResponse> execute(HttpUriRequest request) {
        CompletableFuture<HttpResponse> future = new CompletableFuture<>();
        getAsyncClient().execute(request, new FutureCallback<HttpResponse>() {
            @Override
            public void completed(HttpResponse result) {
                future.complete(result);
            }

            @Override
            public void failed(Exception ex) {
                future.completeExceptionally(ex);
            }

            @Override
            public void cancelled() {
                future.cancel(true);
            }
        });

        return future;
    }

    private HttpAsyncClient getAsyncClient() {
        if (httpAsyncClient != null) {
            return httpAsyncClient;
        }
        synchronized (this) {
            try {
                TrustManager[] tm = new TrustManager[]{new MultiplexHttpClient.NoTrustManager()};
                SSLContext ssl = SSLContext.getInstance("TLS");
                ssl.init((KeyManager[]) null, tm, new SecureRandom());

                RequestConfig requestConfig = RequestConfig.custom()
                        .setConnectTimeout(timeout)
                        .setSocketTimeout(timeout)
                        .setRedirectsEnabled(true)
                        .setCircularRedirectsAllowed(false)
                        .build();

                CloseableHttpAsyncClient newClient = HttpAsyncClients.custom()
                        .setSSLHostnameVerifier(new MultiplexHttpClient.NoHostnameVerifier())
                        .setSSLContext(ssl)
                        .setDefaultRequestConfig(requestConfig)
                        .setMaxConnTotal(1000)
                        .setMaxConnPerRoute(1000)
                        .build();
                newClient.start();
                httpAsyncClient = newClient;
            } catch (NoSuchAlgorithmException | KeyManagementException e1) {
                throw new RuntimeException("初始化HTTP客户端时发生错误", e1);
            }
        }
        return httpAsyncClient;
    }

    private static class NoTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    private static class NoHostnameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    public static class Request {
        /**
         * 一些基础HTTP请求头，这些请求头不能手动添加
         */
        private static final String[] BASIC_HEADERS = new String[] {
                HTTP.CONTENT_LEN,
                HTTP.CONN_DIRECTIVE,
                HTTP.TARGET_HOST};
        private MultiplexHttpClient client;
        private String url;
        private Map<String, String> header = new HashMap<>();
        private Map<String, List<String>> params = new HashMap<>();

        private String jsonBody;
        private byte[] byteStreamBody;

        public Request(MultiplexHttpClient client) {
            this.client = client;
        }

        public Request url(String url) {
            if (url == null || url.isEmpty()) {
                throw new IllegalArgumentException();
            }

            this.url = url;
            return this;
        }

        public Request setHeader(String key, String val) {
            if (key == null || key.isEmpty() || val == null || val.isEmpty()) {
                throw new IllegalArgumentException();
            }

            // 这些基础HTTP请求头就不报错了，直接当无事发生
            if (isBasicRequestHeader(key)) {
                throw new IllegalArgumentException(MessageFormat.format("此HTTP请求头不允许设置:{0}", key));
            }

            header.put(key, val);
            return this;
        }

        public Request addParam(String key, String val) {
            if (key == null || key.isEmpty() || val == null || val.isEmpty()) {
                throw new IllegalArgumentException();
            }

            params.computeIfAbsent(key, k -> new ArrayList<>()).add(val);
            return this;
        }

        public Request jsonBody(String jsonBody) {
            if (jsonBody == null || jsonBody.isEmpty()) {
                throw new IllegalArgumentException();
            }

            this.jsonBody = jsonBody;
            return this;
        }

        public Request byteStreamBody(byte[] byteStreamBody) {
            if (byteStreamBody == null || byteStreamBody.length == 0) {
                throw new IllegalArgumentException();
            }

            this.byteStreamBody = byteStreamBody;
            return this;
        }

        public CompletableFuture<HttpResponse> doPost() {
            argumentValidate();

            URIBuilder uriBuilder = new URIBuilder(URI.create(url));

            // TODO 这里应该优化下，各类型body的取值优先级，或者在赋值的时候给个错误提示
            HttpEntity entity;
            if (jsonBody != null && !jsonBody.isEmpty()) {
                entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON);
                params.forEach((k, v) -> {
                    v.forEach(val -> uriBuilder.addParameter(k, val));
                });
            } else if (byteStreamBody != null && byteStreamBody.length > 0) {
                entity = new ByteArrayEntity(byteStreamBody);
            } else {
                List<BasicNameValuePair> bodyParamList = new ArrayList<>();

                params.forEach((k, v) -> {
                    v.forEach(val -> bodyParamList.add(new BasicNameValuePair(k, val)));
                });
                entity = new UrlEncodedFormEntity(bodyParamList, StandardCharsets.UTF_8);
            }

            URI targetUri = null;
            try {
                targetUri = uriBuilder.build();
            } catch (URISyntaxException e) {
                throw new RuntimeException("URL地址格式错误", e);
            }

            HttpPost req = new HttpPost(targetUri);
            req.setEntity(entity);

            if (header != null) {
                header.forEach(req::addHeader);
            }

            return client.execute(req);
        }

        public CompletableFuture<HttpResponse> doGet() {
            argumentValidate();

            URIBuilder uriBuilder = new URIBuilder(URI.create(url));
            params.forEach((k, v) -> {
                v.forEach(val -> uriBuilder.addParameter(k, val));
            });

            URI targetUri = null;
            try {
                targetUri = uriBuilder.build();
            } catch (URISyntaxException e) {
                throw new RuntimeException("URL地址格式错误", e);
            }

            HttpGet req = new HttpGet(targetUri);
            if (header != null) {
                header.forEach(req::addHeader);
            }

            return client.execute(req);
        }

        private boolean argumentValidate() {
            if (url == null || url.isEmpty()) {
                throw new IllegalArgumentException("url不得为空");
            }

            return true;
        }

        /**
         * 判断是否是基础请求头
         * @param key
         * @return
         */
        private boolean isBasicRequestHeader(String key) {
            if (key == null || key.isEmpty()) {
                return false;
            }

            for (int i = 0; i < BASIC_HEADERS.length; i++) {
                if (BASIC_HEADERS[i].equalsIgnoreCase(key)) {
                    return true;
                }
            }

            return false;
        }
    }
}
